﻿namespace Blaze.FileSystems
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using Utilities;

    /// <summary>
    /// 索引文件，用来维护一个版本的文件列表。
    /// </summary>
    public class IndexFile : IEnumerable<IndexEntry>
    {
        public struct EntryTableItem
        {
            /// <summary>
            /// 标记文件是普通文件还是整合包文件。
            /// 0 - 普通资源
            /// 1 - 整合包
            /// </summary>
            public byte Type;

            /// <summary>
            /// 用于计算文件真实路径的哈希值。
            /// </summary>
            public byte[] Hash;

            public void Write(BinaryWriter writer)
            {
                writer.Write(Type);
                writer.Write(Hash);
            }

            public static EntryTableItem Read(BinaryReader reader)
            {
                var ret = new EntryTableItem
                {
                    Type = reader.ReadByte(),
                    Hash = reader.ReadBytes(20),
                };
                return ret;
            }
        }

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region IEnumerable<IndexEntry> Members

        public IEnumerator<IndexEntry> GetEnumerator()
        {
            return mPathToEntry.Values.GetEnumerator();
        }

        #endregion

        private IndexFile(Stream input)
        {
            var pkgFile = PkgFile.Read(input);
            mPathToEntry = new Dictionary<ulong, IndexEntry>(16);
            foreach (var hash in pkgFile.Hashes)
            {
                var indexEntryStream = pkgFile.Read(hash);
                var reader = new BinaryReader(indexEntryStream);
                var entryTableItem = EntryTableItem.Read(reader);
                var indexEntry = new IndexEntry
                {
                    Hash = entryTableItem.Hash,
                    Path = hash,
                    Size = (int) indexEntryStream.Length,
                    Type = (IndexEntryType) entryTableItem.Type,
                };
                mPathToEntry.Add(indexEntry.Path, indexEntry);
            }
        }

        public static string GetHashedPath(byte[] bytes)
        {
            var buffer = new StringBuilder();
            buffer.AppendFormat("{0:x2}/", bytes[0]);
            for (var i = 1; i < bytes.Length; i++)
                buffer.AppendFormat("{0:x2}", bytes[i]);
            return buffer.ToString();
        }

        public static IndexFile Read(string path)
        {
            using (var fs = File.Open(path, FileMode.Open))
            {
                return Read(fs);
            }
        }

        public static IndexFile Read(Stream input)
        {
            if (input == null)
                throw new ArgumentNullException(nameof(input));
            var ret = new IndexFile(input);
            return ret;
        }

        public IndexEntry GetEntry(ulong hash)
        {
            IndexEntry ret;
            return mPathToEntry.TryGetValue(hash, out ret) ? ret : null;
        }

        public IndexEntry GetEntry(string path)
        {
            var hash = XXHash64.Hash(path);
            return GetEntry(hash);
        }

        private readonly Dictionary<ulong, IndexEntry> mPathToEntry;
    }
}